import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { BoundingBox } from "@babylonjs/core/Culling/boundingBox";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { Scene } from "@babylonjs/core/scene";
import { ANCHOR } from "../control/abstract-base";
import { Utils } from "../../tool/utils";
import { AbstractControl } from "../control/abstract-control";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";

/**
 * 组合ui抽象类参数对象
 * @param anchor 锚点类型
 * @param canvasSize 内部控件画布尺寸
 * @param left 横行位置
 * @param top 纵向位置
 * @param depth 前向位置
 * @param key 键名（默认为名称）
 */
interface IAbstractCompositeOptions {
    anchor?: number,
    canvasSize?: number,
    left?: number,
    top?: number,
    depth?: number,
    key?: string,
}

/**
 * 组合ui抽象类
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
abstract class AbstractComposite {

    protected options: IAbstractCompositeOptions;
    protected elementContainer: TransformNode;
    protected boundControls: AbstractControl[] = [];

    /**
     * 名称
     */
    public readonly name: string;
    /**
     * 所在场景
     */
    public readonly scene: Scene;
    /**
     * 根节点
     */
    public readonly container: TransformNode;
    public readonly id = Utils.id;
    public readonly key: string;
    /**
     * 组合ui标识
     */
    public readonly isComposite = true;

    /**
     * 包围盒
     */
    public readonly boundBox = new BoundingBox(Vector3.Zero(), Vector3.Zero());

    constructor(name: string, scene: Scene, options?: IAbstractCompositeOptions) {
        this.name = name;
        this.key = name;
        this.scene = scene;
        this.elementContainer = new TransformNode(this.name + '-element-container', scene);
        this.container = new TransformNode(this.name + '-root', scene);
        this.elementContainer.parent = this.container;
        this.options = this.initializeOptions(options || {});
        this.left = this.options.left!;
        this.top = this.options.top!;
        this.depth = this.options.depth!;
        if (this.options.key) this.key = this.options.key;
        this.initializeComponents();
        this.computeBoundBox();
        this.setAnchor();
    }

    /**
     * 初始化参数对象
     * @param options 参数对象
     * @returns 初始化后的参数对象
     */
    protected initializeOptions(options: IAbstractCompositeOptions): IAbstractCompositeOptions {
        options.anchor = options.anchor || ANCHOR.CENTER;
        options.canvasSize = options.canvasSize || 1024;
        options.left = options.left || 0;
        options.top = options.top || 0;
        options.depth = options.depth || 0;
        return options;
    }

    /**
     * 初始化组件
     */
    protected abstract initializeComponents(): void;

    /**
     * 计算包围盒
     */
    private computeBoundBox(): void {
        if (!this.boundControls.length) return;
        const min = new Vector3(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
        const max = new Vector3(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
        const mi = new Vector3();
        const ma = new Vector3();
        for (const ctl of this.boundControls) {
            const ms = ctl.container as AbstractMesh;
            const bd = ms.getBoundingInfo().boundingBox;
            mi.copyFrom(bd.minimum);
            mi.x *= ms.scaling.x;
            mi.y *= ms.scaling.y;
            mi.z *= ms.scaling.z;
            mi.x += ms.position.x;
            mi.y += ms.position.y;
            mi.z += ms.position.z;
            ma.copyFrom(bd.maximum);
            ma.x *= ms.scaling.x;
            ma.y *= ms.scaling.y;
            ma.z *= ms.scaling.z;
            ma.x += ms.position.x;
            ma.y += ms.position.y;
            ma.z += ms.position.z;
            if (min.x > mi.x) min.x = mi.x;
            if (min.y > mi.y) min.y = mi.y;
            if (min.z > mi.z) min.z = mi.z;
            if (max.x < ma.x) max.x = ma.x;
            if (max.y < ma.y) max.y = ma.y;
            if (max.z < ma.z) max.z = ma.z;
        }
        this.boundBox.reConstruct(min, max);
    }

    /**
     * 设置锚点
     */
    private setAnchor(): void {
        const cent = this.boundBox.center;
        const min = this.boundBox.minimum;
        const max = this.boundBox.maximum;
        const ofs = new Vector3();
        switch (this.options.anchor!) {
            case ANCHOR.CENTER:
                ofs.x = -cent.x;
                ofs.y = -cent.y;
                break;
            case ANCHOR.CENTER_BOTTOM:
                ofs.x = -cent.x;
                ofs.y = -min.y;
                break;
            case ANCHOR.CENTER_TOP:
                ofs.x = -cent.x;
                ofs.y = -max.y;
                break;
            case ANCHOR.LEFT_CENTER:
                ofs.x = -min.x;
                ofs.y = -cent.y;
                break;
            case ANCHOR.RIGHT_CENTER:
                ofs.x = -max.x;
                ofs.y = -cent.y;
                break;
            case ANCHOR.LEFT_TOP:
                ofs.x = -min.x;
                ofs.y = -max.y;
                break;
            case ANCHOR.LEFT_BOTTOM:
                ofs.x = -min.x;
                ofs.y = -min.y;
                break;
            case ANCHOR.RIGHT_TOP:
                ofs.x = -max.x;
                ofs.y = -max.y;
                break;
            case ANCHOR.RIGHT_BOTTOM:
                ofs.x = -max.x;
                ofs.y = -min.y;
                break;
        }
        this.elementContainer.position.x += ofs.x;
        this.elementContainer.position.y += ofs.y;
        const mn = new Vector3().copyFrom(this.boundBox.minimum).addInPlace(ofs);
        const mx = new Vector3().copyFrom(this.boundBox.maximum).addInPlace(ofs);
        this.boundBox.reConstruct(mn, mx);
    }

    /**
     * 左侧位置
     */
    public get left(): number {
        return this.container.position.x;
    }

    public set left(x: number) {
        this.container.position.x = x;
    }

    /**
     * 顶部位置
     */
    public get top(): number {
        return this.container.position.y;
    }

    public set top(y: number) {
        this.container.position.y = y;
    }

    /**
     * 前向位置
     */
    public get depth(): number {
        return this.container.position.z;
    }

    public set depth(z: number) {
        this.container.position.z = z;
    }

    /**
     * 宽
     */
    public get width(): number {
        return this.boundBox.extendSize.x * 2;
    }

    /**
     * 高
     */
    public get height(): number {
        return this.boundBox.extendSize.y * 2;
    }

    /**
     * 销毁
     */
    public dispose(): void {
        this.container.dispose(false);
    }

}


export { AbstractComposite };
export type { IAbstractCompositeOptions };